# -*- coding: utf-8 -*-

# Copyright (C) 2017 Intel Corporation.  All rights reserved.
# Copyright (c) WanSheng Intelligent Corp. All rights reserved.

#
# Licensed under the Apache License, Version 2.0 (the "License");
#

import logging
import json
from builtins import int

from coapthon.server.coap import CoAP
from coapthon.defines import Codes
from .data_utility import DataUtility
from .data_utility import DataMonitorParam
from wa_edge_iot.internals.rd_utility import RDUtility
from wa_edge_iot.internals.my_coap_server import MyCoapServer
from wa_edge_iot.internals.my_coap_server import SimpleDataMonitorResource
from wa_edge_iot.internals.my_coap_server import SimpleRdMonitorResource

from wa_edge_iot.framework.rd_query_param import RDQueryParam

from .handler_base import CoAPHandlersBase
from .handler_base import CacheDbHandlersBase

from .singleton import singleton
from .cacheDB_utility import CacheDbUtility

from wa_edge_iot.model.parser_base import ParserBase
from wa_edge_iot.model.constants import MediaTypeFormat



'''
 * The I_wagent class is a collection of methods to access all resources on the gateway managed by iAgent.<br><br>
 * 
 * This is the major access entry for all resources under iAgent. In any APP, I_wagent() shall be called firstly 
 * to get the singleton instance.<br><br>
 * 
 * To monitor the status change of device and resource, the API add_device_monitor() is available for this purpose. 
 * Your application can use the RDQueryParam to specify the monitoring condition, and call add_device_monitor() 
 * multiple times for different filters.<br><br>
 * 
 * To get the data of resources managed by the iAgent, the application can use one-time call get(), or use the 
 * data monitor API add_data_monitor_point() for automatically data reporting. The listener API for the data monitor is:<br>
 * your_data_handler(deviceID, resouceUri, ResourceDataGeneral data, DataProcess process)<br>
 * 
 * The content of reported date is provided by the parameter of class ResourceDataGeneral object in the callback API. 
*  As the data reporting was actually implemented in a COAP message from iAgent to the user application, the COAP payload 
 * is parsed and converted to a object of class ResourceDataGeneral or its extended subclasses. The SDK already implemented 
 * two data parsers for LWM2M and OCF payload format, which output the instance of  ResourceDataLWM2M  and  
 * ResourceDataOCF (subclass of ResourceDataGeneral) respectively. The SDK provides a mechanism for adding user defined 
 * payload parser through calling API add_payload_parser().<br><br>
 *
 * @version 1.0
 * @since Dec 2017
'''




class AgentStatus(object):
    def __init__(self, wagent_id, ilink_status, startup_time, enable_redis, redis_working):
        self.__wagent_id = wagent_id
        
        self.ilink_status = ilink_status
        self.startup_time = startup_time
        self.enable_redis = enable_redis
        self.redis_working = redis_working
        
        # ensure no coapth debugging output
        logger = logging.getLogger('coapthon')
        logger.setLevel(logging.WARN)
        
        
    def wagent_id(self):
        return self.__wagent_id 
    
    def dump(self):
        print("wagent status:")
        print("\twagent id: " + self.__wagent_id)
        print("\tilink: " + self.ilink_status)
        print("\tstartup: " + self.startup_time)
            
            
            
@singleton
class I_wagent(object):
    def __init__(self, app_id, 
                 CoAP_handlers = None,
                 CacheDb_handlers = None,
                 wagent_ip="127.0.0.1",
                 wagent_port=5683,
                 redis_port = 6379,
                 monitor_listen_port = None,
                 do_init = True):
        
        self.__wagent_id = None
        self.__app_id = None
        self.__handlers= None
        self.__agent_ip = None
        self.__wagent_port = None
        self.__data_utility = None
        self.__rd_utility = None
        self.__listen_port = None
        self.__coap_server_thread = None
        self.__simple_dm_resource = None
        self.__simple_rdm_resource = None
        self.__cache_util = None
        
        if do_init:
            self.initialize(app_id,
                        CoAP_handlers,
                        CacheDb_handlers,
                        wagent_ip,
                        wagent_port,
                        redis_port,
                        monitor_listen_port)

    
    def initialize(self, app_id, 
                 CoAP_handlers = None,
                 CacheDb_handlers = None,
                 wagent_ip="127.0.0.1",
                 wagent_port=5683,
                 redis_port = 6379,
                 monitor_listen_port = None):    
        
        print("I_wagent.initialize enter..")
        if CoAP_handlers is not None:
            assert isinstance(CoAP_handlers, CoAPHandlersBase)
        if CacheDb_handlers is not None:
            assert isinstance(CacheDb_handlers, CacheDbHandlersBase)
        
        self.__wagent_id = None
        self.__app_id = app_id
        
        self.__handlers = CoAP_handlers
        self.__agent_ip = wagent_ip
        self.__wagent_port = wagent_port
        self.__data_utility = DataUtility(self, ip=wagent_ip, port=wagent_port)
        self.__rd_utility = RDUtility(self, ip=wagent_ip, port=wagent_port)
        
        if self.__coap_server_thread:
            self.stop_monitoring()
            self.__coap_server_thread = None
            
        if CoAP_handlers:
            if monitor_listen_port is None:
                from wa_edge_iot.internals.my_coap_server import find_listen_port
                monitor_listen_port = find_listen_port()
                
            self.__listen_port = monitor_listen_port
                
            coap_server = CoAP(("127.0.0.1", monitor_listen_port))
            self.__simple_dm_resource = SimpleDataMonitorResource(user_callback=CoAP_handlers)
            self.__simple_rdm_resource = SimpleRdMonitorResource(user_callback=CoAP_handlers)
            coap_server.add_resource("/dm", self.__simple_dm_resource)
            coap_server.add_resource("/rdm", self.__simple_rdm_resource)
            self.__coap_server_thread = MyCoapServer(coap_server);
        
            self.start_monitoring()
        
        # if redis is enabled, don't use the CoAP put for data notification.
        if (self.__cache_util):
            self.__cache_util.quit();
            self.__cache_util = None
        if (redis_port):
            self.__cache_util = CacheDbUtility(CacheDb_handlers, redis_port)
        else:
            self.__cache_util = None
            
                    
    def app_id(self):
        return self.__app_id;
    
    def wagent_id(self):
        if self.__wagent_id is None:
            info = self.get_wagent_info()
            if info:
                self.__wagent_id = info.wagent_id()

        return self.__wagent_id

    def get_wagent_info(self):
        response = self.__data_utility.get("ilink")
        if response is None or len(response.payload) == 0:
            print("get_wagent_info: no response for /ilink")
            return None
        
        try:
            import json
            info = json.loads(response.payload)
            return AgentStatus(info["iagent_id"], 
                                info["cloud"], 
                                info["start_time"],
                                info.get("enable-redis"),
                                info.get("redis-working"))
        except Exception as e:
            print("get_wagent_info excpetion:" + str(e))
            return None
        

    def listen_port(self):
        return self.__listen_port;
    
    def data_util(self):
        return self.__data_utility
    
    def redis_util(self):
        return self.__cache_util
            
    def set_coap_handlers(self, handlers):
        self.__simple_dm_resource.set_user_handler(handlers)
        self.__simple_rdm_resource.set_user_handler(handlers)
        
        
    '''
     * Method to create a monitor for value change of attribute in devices, groups or resources.
     * This method is synchronous, that means this method maybe blocked for network issue, but
     * there is a 2s timer for timeout. 
     * 
     * @parameters monitor_name: when cacheDB redis is connect, value "None" means on_resource_data_handler()
     * won't be enabled.
     * @parameters process: True - arrived data will handled in on_resource_data_process()
     
     * @see monitor_name is caller defined name for the monitor point created with the parameters. 
 
    '''
    def add_data_monitor_point(self, 
                               di , 
                               resource_uri, 
                               property_name = None,
                               monitor_name = None,
                               interval=10, 
                               sequence=0, 
                               process=False):
        
        assert type(interval) is int
        assert type(sequence) is int
        assert type(resource_uri) in [str, str]
        
        #
        parameters = DataMonitorParam(di, resource_uri, property_name, interval,sequence, process , monitor_name)
        result = self.__data_utility.add_data_observer(parameters)
        
        return result
        
    
    '''
     * Method to delete a monitor for value change of attribute in devices, groups or resources.
     * This method is synchronous, that means this method maybe blocked for network issue, but
     * there is a 2s timer for timeout. 
     * 
     * @parameters data_monitor_id ID of this monitor, which is from returned value of API add_data_monitor_point.
     * @return the ID of monitor point created in wagent with 

     * @see parameters is a instance of wagent.framework.data_utility.DataMonitorParam
     * @see monitor_name is caller defined name for the monitor point created with the parameters. 
 
    '''
    
    def remove_data_monitor_point(self, monitor_id):
        return self.__data_utility.remove_data_observer(monitor_id)

    '''
     * Method to add a monitor for device registriation change of devices. 
     * This method is synchronous, that means this method maybe blocked for network issue, but
     * there is a 2s timer for timeout. 
     * 
     * @param parameters This parameter is for different query condition of target device, group or resource.
     * @return ID of this monitor, which shall be stored by APP and used when this RD monitor to deleted.
     * 
     * @see parameters is a instance of wagent.framework.rdutility.RDQueryParam
     * @see monitor_name is caller defined name for the monitor point created with the parameters. 
    '''
    def add_device_monitor(self,  monitor_name = "", parameters = None ):
        assert isinstance(parameters, RDQueryParam) or parameters is None
        return self.__rd_utility.create_monitor( monitor_name, parameters)

    def remove_device_monitor_point(self, monitor_id):
        return self.__rd_utility.remove_monitor(monitor_id)


    '''
     * Method to register the parser for resource property on special device. 
     * 
     * @param formatType The format of resource property on special device, which shall not duplicate with the result of API getFormatSupported.
     * This formatType can be self-defined.
     * @param parser This parameter is the parser implementation for resource property on special device.
     * 
     * @see wagent.utilities.payload_parser.PayloadParser
    '''
    def add_payload_parser(self, format_type, parser):
        assert isinstance(parser, ParserBase)
        return self.__data_utility.register_payload_parser(format_type, parser)

    '''
    *  @start_time: None mean the alarm is raised at the calling time
    *   @ clear_time Not None means the alarm is cleared at the time
    '''
    def post_alarm(self, alarm_id,  target_type, target_id,  
                   title, content, severity=0,
                   group = None, 
                   start_time = None,  clear_time = None, project = None):
        req = {}
        req['alarmid'] = alarm_id
        req['title'] = title
        req['targetid'] = target_id
        req['targettype'] = target_type
        req['content'] = content
        req['severity'] = severity
        if group:
            req['group'] = group
        if  start_time: 
            req['set_t'] = start_time
        if clear_time:
            req['clear_t'] = clear_time
        if project:
            req['project'] = project
            
        return self.data_util().put("/alarm", 
                             MediaTypeFormat.APPLICATION_JSON , 
                             json.dumps(req), 
                              1)
        
        
    def clear_alarm(self, alarm_id,  target_type, target_id, reason):
        req = {}
        req['alarmid'] = alarm_id
        req['targetid'] = target_id
        req['targettype'] = target_type
        req['clear_reason'] = reason
        return self.data_util().put("/alarm?action=clear", 
                             MediaTypeFormat.APPLICATION_JSON , 
                             json.dumps(req), 
                              1)

    def query_alarm(self, alarm_id,  target_type, target_id = None,  
                   time_begin = None,  time_end = None, active_only=False, query_stats=False):

        uri = '/alarm'
        if(query_stats):
            uri = '/alarm_stats'
        uri += "?a=a"
        if(alarm_id):
            uri += f'&alarmid={alarm_id}'
        if(target_type):
            uri += f'&tt={target_type}'
        if target_id:
            uri += f'&ti={target_id}'
        if( active_only):
            uri += f'&status=active'
        if (time_begin != None):
            uri += f'&begin={time_begin}'
        if (time_end != None):
            uri += f'&end={time_end}'
        print ('query_alarm:' + uri)
        return self.data_util().get(uri, 
                             MediaTypeFormat.APPLICATION_JSON)


    def post_event(self, event_id,  target_type, target_id,  
                   title, content, severity=0):
        req = {}
        req['eventid'] = event_id
        req['title'] = title
        req['targetid'] = target_id
        req['targettype'] = target_type
        req['content'] = content
        req['severity'] = severity
            
        return self.data_util().put("/event", 
                             MediaTypeFormat.APPLICATION_JSON , 
                             json.dumps(req), 
                              1)
        

    def query_event(self, event_id,  target_type, target_id = None,  
                   time_begin = None,  time_end = None):

        uri = '/event?a=a'
        if(event_id):
            uri += f'&alarmid={event_id}'
        if(target_type):
            uri += f'&tt={target_type}'
        if target_id:
            uri += f'&ti={target_id}'
        if (time_begin != None):
            uri += f'&begin={time_begin}'
        if (time_end != None):
            uri += f'&end={time_end}'
        print ('query_event:' + uri)
        return self.data_util().get(uri, 
                             MediaTypeFormat.APPLICATION_JSON)
    '''
     * Method to get devices that is specified by parameter query. 
     * 
     * @param query This parameter is for different query condition of target devices.
     * @return The specified device by parameter query if existing on the gateway or NULL if not existing.
     * 
     * @see wagent.model.rd_query_param.RDQueryParam
    '''
    def do_device_query(self, query = None):
        if query:
            assert isinstance(query, RDQueryParam)
            
        return self.__rd_utility.query_rd(query)


    def start_monitoring(self):
        
        if self.__coap_server_thread.is_started():
            return
        
        self.__coap_server_thread.start_server()


    def stop_monitoring(self):
        assert self.__coap_server_thread
        self.__coap_server_thread.stop_server()
    
    
    def add_my_service(self, uri, resource):   
        assert self.__coap_server_thread 
        from coapthon.resources.resource import Resource
        assert isinstance(resource, Resource) 
        
        self.__coap_server_thread.get_coap_server().add_resource(uri, resource)
        
